package it.unibo.refolding.pico;

import com.google.common.base.Preconditions;

public class LongArrayRegression {

  private final long[] sumY;
  private final long[] sumY2;
  private final long[] sumXy;

  private LongArrayRegression(long[] sumY, long[] sumY2, long[] sumXy) {
    Preconditions.checkArgument(sumY2.length == sumY.length);
    Preconditions.checkArgument(sumXy.length == sumY.length);
    this.sumY = sumY;
    this.sumY2 = sumY2;
    this.sumXy = sumXy;
  }

  public static LongArrayRegression create(short[] y) {
    long[] sumY = new long[y.length];
    long[] sumY2 = new long[y.length];
    long[] sumXy = new long[y.length];
    long prevSumY = 0;
    long prevSumY2 = 0;
    long prevSumXy = 0;
    for (int i = 0; i < y.length; ++i) {
      long element = y[i];
      sumY[i] = prevSumY += element;
      sumY2[i] = prevSumY2 += element * element;
      sumXy[i] = prevSumXy += i * element;
    }
    return new LongArrayRegression(sumY, sumY2, sumXy);
  }

  public double rSquare(int from, int to) {
    int n = to - from;
    long sumXy = sumXy(from, to);
    long sumX = sumX(from, to);
    long sumY = sumY(from, to);
    long sxy = LongArrayRegression.sxy(n, sumX, sumY, sumXy);
    long sumX2 = sumX2(from, to);
    long sumY2 = sumY2(from, to);
    long sxx = LongArrayRegression.sxx(n, sumX, sumX2);
    long syy = LongArrayRegression.syy(n, sumY, sumY2);
    return rSquare(sxx, sxy, syy);
  }

  public static double rSquare(long sxx, long sxy, long syy) {
    // return CommonThings.squareNumeratorAndDivide(sxy, Math.max(sxx, syy)) /
    // Math.min(sxx, syy);
    return (sxy / (double) sxx) * (sxy / (double) syy);
  }

  public double slope(int from, int to) {
    int n = to - from;
    long sumXy = sumXy(from, to);
    long sumX = sumX(from, to);
    long sumX2 = sumX2(from, to);
    long sumY = sumY(from, to);
    long sxy = LongArrayRegression.sxy(n, sumX, sumY, sumXy);
    long sxx = LongArrayRegression.sxx(n, sumX, sumX2);
    // return CommonThings.fractionToDouble(sxy, sxx);
    return slope(sxy, sxx);
  }

  public static double slope(long sxy, long sxx) {
    return sxy / (double) sxx;
  }

  public double sumOfSquaredErrors(int from, int to) {
    int n = to - from;

    long sumX = sumX(from, to);
    long sumX2 = sumX2(from, to);
    long sxx = LongArrayRegression.sxx(n, sumX, sumX2);

    long sumY = sumY(from, to);
    long sumY2 = sumY2(from, to);
    long syy = LongArrayRegression.syy(n, sumY, sumY2);

    long sumXy = sumXy(from, to);
    long sxy = LongArrayRegression.sxy(n, sumX, sumY, sumXy);

    return sumOfSquaredErrors(n, sxx, sxy, syy);
  }

  public static double sumOfSquaredErrors(int n, long sxx, long sxy, long syy) {
    assert sxx >= 0 : sxx;
    assert syy >= 0 : syy;
    return (syy - sxy / (double) sxx * sxy) / n;
  }

  public long sumX(int from, int to) {
    long longTo = (long) to;
    return (longTo - from) * (longTo + from - 1) / 2;
  }

  public long sumX2(int from, int to) {
    long longTo = (long) to;
    long longN = to - from;
     return longN * ((longN * longN << 1) + 6 * from * longTo - 3 * (from + longTo) + 1) / 6;
  }

  public long sumXy(int from, int to) {
    return sumXy[to - 1] - (from == 0 ? 0 : sumXy[from - 1]);
  }

  public long sumY(int from, int to) {
    return sumY[to - 1] - (from == 0 ? 0 : sumY[from - 1]);
  }

  public long sumY2(int from, int to) {
    return sumY2[to - 1] - (from == 0 ? 0 : sumY2[from - 1]);
  }

  public long sxx(int from, int to) {
    int n = to - from;
    long sumX2 = sumX2(from, to);
    long sumX = sumX(from, to);
    return LongArrayRegression.sxx(n, sumX, sumX2);
  }

  public long sxy(int from, int to) {
    int n = to - from;
    long sumX = sumX(from, to);
    long sumY = sumY(from, to);
    long sumXy = sumXy(from, to);
    return LongArrayRegression.sxy(n, sumX, sumY, sumXy);
  }

  public static long sxx(int n, long sumX, long sumX2) {
    return n * sumX2 - sumX * sumX;
  }

  public static long sxy(int n, long sumX, long sumY, long sumXy) {
    return n * sumXy - sumX * sumY;
  }

  public static long syy(int n, long sumY, long sumY2) {
    return n * sumY2 - sumY * sumY;
  }
}
